/*
  *  Copyright (c) 2007, 2008, Andrea Bittau <a.bittau@cs.ucl.ac.uk>
  *
  *  OS dependent API for FreeBSD.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/endian.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <net/bpf.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/if_media.h>
#include <sys/ioctl.h>
#include <net/if_dl.h>
#ifdef __DragonFly__
#include <netproto/802_11/ieee80211_ioctl.h>
#include <netproto/802_11/ieee80211_radiotap.h>
#include <netproto/802_11/ieee80211_dragonfly.h>
#else
#include <net80211/ieee80211_ioctl.h>
#include <net80211/ieee80211_radiotap.h>
#include <net80211/ieee80211_freebsd.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/uio.h>
#include <assert.h>
#include <ifaddrs.h>

#include "osdep.h"

#ifndef LINKTYPE_IEEE802_11
#define LINKTYPE_IEEE802_11 105
#endif

struct priv_fbsd
{
	/* iface */
	int pf_fd;

	/* rx */
	int pf_nocrc;

	/* tx */
	unsigned char pf_buf[4096];
	unsigned char * pf_next;
	int pf_totlen;
	struct ieee80211_bpf_params pf_txparams;

	/* setchan */
	int pf_s;
	struct ifreq pf_ifr;
	struct ieee80211req pf_ireq;
	int pf_chan;
};

/* from ifconfig */
static __inline int mapgsm(u_int freq, u_int flags)
{
	freq *= 10;
	if (flags & IEEE80211_CHAN_QUARTER)
		freq += 5;
	else if (flags & IEEE80211_CHAN_HALF)
		freq += 10;
	else
		freq += 20;
	/* NB: there is no 907/20 wide but leave room */
	return (freq - 906 * 10) / 5;
}

static __inline int mappsb(u_int freq)
{
	return 37 + ((freq * 10) + ((freq % 5) == 2 ? 5 : 0) - 49400) / 5;
}

/*
 * Convert MHz frequency to IEEE channel number.
 */
static u_int ieee80211_mhz2ieee(u_int freq, u_int flags)
{
	if ((flags & IEEE80211_CHAN_GSM) || (907 <= freq && freq <= 922))
		return mapgsm(freq, flags);
	if (freq == 2484) return 14;
	if (freq < 2484) return (freq - 2407) / 5;
	if (freq < 5000)
	{
		if (flags & (IEEE80211_CHAN_HALF | IEEE80211_CHAN_QUARTER))
			return mappsb(freq);
		else if (freq > 4900)
			return (freq - 4000) / 5;
		else
			return 15 + ((freq - 2512) / 20);
	}
	return (freq - 5000) / 5;
}
/* end of ifconfig */

static void get_radiotap_info(struct priv_fbsd * pf,
							  struct ieee80211_radiotap_header * rth,
							  int * plen,
							  struct rx_info * ri)
{
	uint32_t present;
	uint8_t rflags = 0;
	int i;
	unsigned char * body = (unsigned char *) (rth + 1);
	int dbm_power = 0, db_power = 0;

	/* reset control info */
	if (ri) memset(ri, 0, sizeof(*ri));

	/* get info */
	present = le32toh(rth->it_present);
	for (i = IEEE80211_RADIOTAP_TSFT; i <= IEEE80211_RADIOTAP_EXT; i++)
	{
		if (!(present & (1 << i))) continue;

		switch (i)
		{
			case IEEE80211_RADIOTAP_TSFT:
				body += sizeof(uint64_t);
				break;

			case IEEE80211_RADIOTAP_FLAGS:
				rflags = *((uint8_t *) body);
			/* fall through */
			case IEEE80211_RADIOTAP_RATE:
				body += sizeof(uint8_t);
				break;

			case IEEE80211_RADIOTAP_CHANNEL:
				if (ri)
				{
					uint16_t * p = (uint16_t *) body;
					int c = ieee80211_mhz2ieee(*p, *(p + 1));

					ri->ri_channel = c;
				}
				body += sizeof(uint16_t) * 2;
				break;

			case IEEE80211_RADIOTAP_FHSS:
				body += sizeof(uint16_t);
				break;

			case IEEE80211_RADIOTAP_DBM_ANTSIGNAL:
				dbm_power = *body++;
				break;

			case IEEE80211_RADIOTAP_DBM_ANTNOISE:
				dbm_power -= *body++;
				break;

			case IEEE80211_RADIOTAP_DB_ANTSIGNAL:
				db_power = *body++;
				break;

			case IEEE80211_RADIOTAP_DB_ANTNOISE:
				db_power -= *body++;
				break;

			default:
				i = IEEE80211_RADIOTAP_EXT + 1;
				break;
		}
	}

	/* set power */
	if (ri)
	{
		if (dbm_power)
			ri->ri_power = dbm_power;
		else
			ri->ri_power = db_power;
	}

	/* XXX cache; drivers won't change this per-packet */
	/* check if FCS/CRC is included in packet */
	if (pf->pf_nocrc || (rflags & IEEE80211_RADIOTAP_F_FCS))
	{
		*plen -= IEEE80211_CRC_LEN;
		pf->pf_nocrc = 1;
	}
}

static unsigned char *
get_80211(struct priv_fbsd * pf, int * plen, struct rx_info * ri)
{
	struct bpf_hdr * bpfh;
	struct ieee80211_radiotap_header * rth;
	void * ptr;
	unsigned char ** data;
	int * totlen;

	data = &pf->pf_next;
	totlen = &pf->pf_totlen;
	assert(*totlen);

	/* bpf hdr */
	bpfh = (struct bpf_hdr *) (*data);
	assert(bpfh->bh_caplen == bpfh->bh_datalen); /* XXX */
	*totlen -= bpfh->bh_hdrlen;

	/* check if more packets */
	if ((int) bpfh->bh_caplen < *totlen)
	{
		int tot = bpfh->bh_hdrlen + bpfh->bh_caplen;
		int offset = BPF_WORDALIGN(tot);

		*data = (unsigned char *) bpfh + offset;
		*totlen -= offset - tot; /* take into account align bytes */
	}
	else if ((int) bpfh->bh_caplen > *totlen)
		abort();

	*plen = bpfh->bh_caplen;
	*totlen -= bpfh->bh_caplen;
	assert(*totlen >= 0);

	/* radiotap */
	rth = (struct ieee80211_radiotap_header *) ((char *) bpfh
												+ bpfh->bh_hdrlen);
	get_radiotap_info(pf, rth, plen, ri);
	*plen -= rth->it_len;
	assert(*plen > 0);

	/* data */
	ptr = (char *) rth + rth->it_len;

	return ptr;
}

static int fbsd_get_channel(struct wif * wi)
{
	struct priv_fbsd * pf = wi_priv(wi);

	if (ioctl(pf->pf_s, SIOCG80211, &pf->pf_ireq) != 0) return -1;

	return pf->pf_ireq.i_val;
}

static int fbsd_read(struct wif * wi,
					 struct timespec * ts,
					 int * dlt,
					 unsigned char * h80211,
					 int len,
					 struct rx_info * ri)
{
	struct priv_fbsd * pf = wi_priv(wi);
	unsigned char * wh;
	int plen;

	assert(len > 0);

	/* need to read more */
	if (pf->pf_totlen == 0)
	{
		pf->pf_totlen = read(pf->pf_fd, pf->pf_buf, sizeof(pf->pf_buf));
		if (pf->pf_totlen == -1)
		{
			pf->pf_totlen = 0;
			return -1;
		}
		pf->pf_next = pf->pf_buf;
	}

	/* read 802.11 packet */
	wh = get_80211(pf, &plen, ri);
	if (plen > len) plen = len;
	assert(plen > 0);
	memcpy(h80211, wh, plen);

	if (dlt)
	{
		*dlt = LINKTYPE_IEEE802_11;
	}

	if (ts)
	{
		clock_gettime(CLOCK_REALTIME, ts);
	}

	if (ri && !ri->ri_channel) ri->ri_channel = wi_get_channel(wi);

	return plen;
}

static int fbsd_write(struct wif * wi,
					  struct timespec * ts,
					  int dlt,
					  unsigned char * h80211,
					  int len,
					  struct tx_info * ti)
{
	struct iovec iov[2];
	struct priv_fbsd * pf = wi_priv(wi);
	int rc;

	(void) ts;
	(void) dlt;

	/* XXX make use of ti */
	if (ti)
	{
	}

	iov[0].iov_base = &pf->pf_txparams;
	iov[0].iov_len = pf->pf_txparams.ibp_len;

	iov[1].iov_base = h80211;
	iov[1].iov_len = len;

	rc = writev(pf->pf_fd, iov, 2);
	if (rc == -1) return rc;
	if (rc < (int) iov[0].iov_len) return 0;

	return rc - iov[0].iov_len;
}

static int fbsd_set_channel(struct wif * wi, int chan)
{
	struct priv_fbsd * pf = wi_priv(wi);

	pf->pf_ireq.i_val = chan;
	if (ioctl(pf->pf_s, SIOCS80211, &pf->pf_ireq) != 0) return -1;

	pf->pf_chan = chan;
	return 0;
}

static void do_free(struct wif * wi)
{
	assert(wi->wi_priv);
	free(wi->wi_priv);
	wi->wi_priv = 0;
	free(wi);
}

static void fbsd_close(struct wif * wi)
{
	struct priv_fbsd * pf = wi_priv(wi);

	close(pf->pf_fd);
	close(pf->pf_s);
	do_free(wi);
}

static int do_fbsd_open(struct wif * wi, char * iface)
{
	int i;
	char buf[64];
	int fd = -1;
	struct ifreq ifr;
	unsigned int dlt = DLT_IEEE802_11_RADIO;
	int s;
	unsigned int flags;
	struct ifmediareq ifmr;
	int * mwords;
	struct priv_fbsd * pf = wi_priv(wi);

	/* basic sanity check */
	if (strlen(iface) >= sizeof(ifr.ifr_name)) return -1;

	/* open wifi */
	s = socket(PF_INET, SOCK_DGRAM, 0);
	if (s == -1) return -1;
	pf->pf_s = s;

	/* set iface up and promisc */
	memset(&ifr, 0, sizeof(ifr));
	strcpy(ifr.ifr_name, iface);
	if (ioctl(s, SIOCGIFFLAGS, &ifr) == -1) goto close_sock;

	flags = (ifr.ifr_flags & 0xffff) | (ifr.ifr_flagshigh << 16);
	flags |= IFF_UP | IFF_PPROMISC;
	memset(&ifr, 0, sizeof(ifr));
	strcpy(ifr.ifr_name, iface);
	ifr.ifr_flags = flags & 0xffff;
	ifr.ifr_flagshigh = flags >> 16;
	if (ioctl(s, SIOCSIFFLAGS, &ifr) == -1) goto close_sock;

	/* monitor mode */
	memset(&ifmr, 0, sizeof(ifmr));
	strcpy(ifmr.ifm_name, iface);
	if (ioctl(s, SIOCGIFMEDIA, &ifmr) == -1) goto close_sock;

	assert(ifmr.ifm_count != 0);

	mwords = (int *) malloc(ifmr.ifm_count * sizeof(int));
	if (!mwords) goto close_sock;
	ifmr.ifm_ulist = mwords;
	if (ioctl(s, SIOCGIFMEDIA, &ifmr) == -1)
	{
		free(mwords);
		goto close_sock;
	}
	free(mwords);

	memset(&ifr, 0, sizeof(ifr));
	strcpy(ifr.ifr_name, iface);
	ifr.ifr_media = ifmr.ifm_current;
	if (ioctl(s, SIOCSIFMEDIA, &ifr) == -1) goto close_sock;

	/* setup ifreq for chan that may be used in future */
	strcpy(pf->pf_ireq.i_name, iface);
	pf->pf_ireq.i_type = IEEE80211_IOC_CHANNEL;

	/* same for ifreq [mac addr] */
	strcpy(pf->pf_ifr.ifr_name, iface);

	/* open bpf */
	for (i = 0; i < 256; i++)
	{
		sprintf(buf, "/dev/bpf%d", i);

		fd = open(buf, O_RDWR);
		if (fd < 0)
		{
			if (errno != EBUSY) return -1;
			continue;
		}
		else
			break;
	}

	if (fd < 0) goto close_sock;

	strcpy(ifr.ifr_name, iface);

	if (ioctl(fd, BIOCSETIF, &ifr) < 0) goto close_bpf;

	if (ioctl(fd, BIOCSDLT, &dlt) < 0) goto close_bpf;

	dlt = 1;
	if (ioctl(fd, BIOCIMMEDIATE, &dlt) == -1) goto close_bpf;

	return fd;

close_sock:
	close(s);
	return -1;
close_bpf:
	close(fd);
	goto close_sock;
}

static int fbsd_fd(struct wif * wi)
{
	struct priv_fbsd * pf = wi_priv(wi);

	return pf->pf_fd;
}

static int fbsd_get_mac(struct wif * wi, unsigned char * mac)
{
	struct ifaddrs *ifa, *p;
	char * name = wi_get_ifname(wi);
	int rc = -1;
	struct sockaddr_dl * sdp;

	if (getifaddrs(&ifa) == -1) return -1;

	p = ifa;
	while (p)
	{
		if (p->ifa_addr->sa_family == AF_LINK && strcmp(name, p->ifa_name) == 0)
		{

			sdp = (struct sockaddr_dl *) p->ifa_addr;
			memcpy(mac, sdp->sdl_data + sdp->sdl_nlen, 6);
			rc = 0;
			break;
		}

		p = p->ifa_next;
	}
	freeifaddrs(ifa);

	return rc;
}

static int fbsd_get_monitor(struct wif * wi)
{
	if (wi)
	{
	} /* XXX unused */

	/* XXX */
	return 0;
}

static int fbsd_get_rate(struct wif * wi)
{
	if (wi)
	{
	} /* XXX unused */

	/* XXX */
	return 1000000;
}

static int fbsd_set_rate(struct wif * wi, int rate)
{
	if (wi || rate)
	{
	} /* XXX unused */

	/* XXX */
	return 0;
}

static int fbsd_set_mac(struct wif * wi, unsigned char * mac)
{
	struct priv_fbsd * priv = wi_priv(wi);
	struct ifreq * ifr = &priv->pf_ifr;

	ifr->ifr_addr.sa_family = AF_LINK;
	ifr->ifr_addr.sa_len = 6;
	memcpy(ifr->ifr_addr.sa_data, mac, 6);

	return ioctl(priv->pf_s, SIOCSIFLLADDR, ifr);
}

static int fbsd_set_mtu(struct wif * wi, int mtu)
{
	struct priv_fbsd * priv = wi_priv(wi);
	struct ifreq * ifr = &priv->pf_ifr;

	memset(ifr, 0, sizeof(struct ifreq));

	strncpy(ifr->ifr_name, wi_get_ifname(wi), sizeof(ifr->ifr_name));
	ifr->ifr_mtu = mtu;

	if (ioctl(priv->pf_s, SIOCSIFMTU, ifr) < 0) return -1;

	return 0;
}

static int fbsd_get_mtu(struct wif * wi)
{
	struct priv_fbsd * priv = wi_priv(wi);
	struct ifreq ifr;

	memset(&ifr, 0, sizeof(struct ifreq));

	ifr.ifr_addr.sa_family = AF_INET;

	strncpy(ifr.ifr_name, wi_get_ifname(wi), sizeof(ifr.ifr_name));

	if (ioctl(priv->pf_s, SIOCGIFMTU, (caddr_t) &ifr) < 0) return -1;

	return ifr.ifr_mtu;
}

static struct wif * fbsd_open(char * iface)
{
	struct wif * wi;
	struct priv_fbsd * pf;
	int fd;

	/* setup wi struct */
	wi = wi_alloc(sizeof(*pf));
	if (!wi) return NULL;
	wi->wi_read = fbsd_read;
	wi->wi_write = fbsd_write;
	wi->wi_set_channel = fbsd_set_channel;
	wi->wi_get_channel = fbsd_get_channel;
	wi->wi_close = fbsd_close;
	wi->wi_fd = fbsd_fd;
	wi->wi_get_mac = fbsd_get_mac;
	wi->wi_set_mac = fbsd_set_mac;
	wi->wi_get_rate = fbsd_get_rate;
	wi->wi_set_rate = fbsd_set_rate;
	wi->wi_get_monitor = fbsd_get_monitor;
	wi->wi_get_mtu = fbsd_get_mtu;
	wi->wi_set_mtu = fbsd_set_mtu;

	/* setup iface */
	fd = do_fbsd_open(wi, iface);
	if (fd == -1)
	{
		do_free(wi);
		return NULL;
	}

	/* setup private state */
	pf = wi_priv(wi);
	pf->pf_fd = fd;
	pf->pf_txparams.ibp_vers = IEEE80211_BPF_VERSION;
	pf->pf_txparams.ibp_len = sizeof(struct ieee80211_bpf_params) - 6;
	pf->pf_txparams.ibp_rate0 = 2; /* 1 MB/s XXX */
	pf->pf_txparams.ibp_try0 = 1; /* no retransmits */
	pf->pf_txparams.ibp_rate1 = 2; /* 1 MB/s XXX */
	pf->pf_txparams.ibp_try1 = 1; /* no retransmits */
	pf->pf_txparams.ibp_flags = IEEE80211_BPF_NOACK;
	pf->pf_txparams.ibp_power = 100; /* nominal max */
	pf->pf_txparams.ibp_pri = WME_AC_VO; /* high priority */

	return wi;
}

struct wif * wi_open_osdep(char * iface) { return fbsd_open(iface); }

EXPORT int get_battery_state(void)
{
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__MidnightBSD__)
	int value;
	size_t len;

	len = 4;
	value = 0;
	sysctlbyname("hw.acpi.acline", &value, &len, NULL, 0);
	if (value == 0)
	{
		sysctlbyname("hw.acpi.battery.time", &value, &len, NULL, 0);
		value = value * 60;
	}
	else
	{
		value = 0;
	}

	return (value);
#elif defined(_BSD_SOURCE)
	struct apm_power_info api;
	int apmfd;
	if ((apmfd = open("/dev/apm", O_RDONLY)) < 0) return 0;
	if (ioctl(apmfd, APM_IOC_GETPOWER, &api) < 0)
	{
		close(apmfd);
		return 0;
	}
	close(apmfd);
	if (api.battery_state == APM_BATT_UNKNOWN
		|| api.battery_state == APM_BATTERY_ABSENT
		|| api.battery_state == APM_BATT_CHARGING
		|| api.ac_state == APM_AC_ON)
	{
		return 0;
	}
	return ((int) (api.minutes_left)) * 60;
#else
	return 0;
#endif
}
